#include "table.h"
#include "error.h"
#include "util.h"
#include "la.h"
#include <string.h>

Bool check_identifier_duplication(char *name, Class clss);
Bool check_normal_duplication(char *name, Class clss);
Bool check_func_duplication(char *name);

FuncElement func_table[MAX_FUNC_NUM];
int func_table_p = 0;
NormalElement normal_table[MAX_TOKEN_NUM];
int normal_table_p = 0;
int temp_var_p = 0;

FuncElement *current_scope = NULL;


//typedef struct FuncElement {
//char name[MAX_TOKEN_LEN];
//Class clss;
//Type type;
//int dcl_row;
//int ref_num;
//int ref_row[];
//} FuncElement;
//
//typedef struct NormalElement {
//char name[MAX_TOKEN_LEN];
//Class clss;
//Type type;
//Value value;
//Bool initd;
//FuncElement *scope;
//int dcl_row;
//int ref_num;
//int ref_row[MAX_REF_NUM];
//} TableElement;

void enter_normal_element(char *name, Class clss, Type type, Value value, int dcl_row)
{
    if (check_identifier_duplication(name, clss) == TRUE) {
        return;
    }
    strcpy(normal_table[normal_table_p].name, name);
    normal_table[normal_table_p].clss = clss;
    normal_table[normal_table_p].type = type;
    normal_table[normal_table_p].value = value;
    if (clss == GLOBAL_VAR || clss == LOCAL_VAR) {
        normal_table[normal_table_p].initd = FALSE;
    }
    else {
        normal_table[normal_table_p].initd = TRUE;
    }
    normal_table[normal_table_p].scope = current_scope;
    normal_table[normal_table_p].dcl_row = dcl_row;
    normal_table[normal_table_p].ref_num = 0;
    normal_table_p++;
}

void reset_temp_p()
{
    temp_var_p = 0;
}

NormalElement *enter_temp_element()
{
    Value value;
    char tv[MAX_TOKEN_LEN] = {0};
    tv[0] = '@';
    tv[1] = 't';
    value.int_val = 0;
    sprintf(&tv[2], "%d", temp_var_p++);
    enter_normal_element(tv, TEMP_VAR, T_INT, value, -1);
    return &normal_table[normal_table_p - 1];
}

void enter_func_element(char *name, Class clss, Type type, int dcl_row)
{
    if (check_identifier_duplication(name, clss) == TRUE) {
        return;
    }
    strcpy(func_table[func_table_p].name, name);
    func_table[func_table_p].clss = clss;
    func_table[func_table_p].type = type;
    func_table[func_table_p].dcl_row = dcl_row;
    func_table[func_table_p].ref_num = 0;
    func_table_p++;
}

void enter_func_param(Type tt)
{
    int i = func_table_p;
    func_table[i].param_list[func_table[i].param_num++] = tt;
}

Bool check_identifier_duplication(char *name, Class clss)
{
    if (check_normal_duplication(name, clss) == TRUE ||
        check_func_duplication(name) == TRUE) {
            return TRUE;
    }
    else {
        return FALSE;
    }
}

Bool check_normal_duplication(char *name, Class clss)
{
    int i;
    for (i = 0; i < normal_table_p; i++) {
        if (strcmp(name, normal_table[i].name) == 0 &&
            is_global_normal_element(clss) == is_global_normal_element(normal_table[i].clss) &&
            current_scope == normal_table[i].scope) {
                print_error("Already declared a identifier with the same name in the same scope.", name, "");
                return TRUE;
        }
    }
    return FALSE;
}

Bool check_func_duplication(char *name)
{
    int i;
    for (i = 0; i < func_table_p; i++) {
        if (strcmp(name, func_table[i].name) == 0) {
            print_error("Already declared a function with the same name in the same scope.", name, "");
            return TRUE;
        }
    }
    return FALSE;
}

FuncElement *func_table_lookup(char *tb)
{
    int i;
    for (i = 0; i < func_table_p; i++) {
        if (strcmp(func_table[i].name, tb) == 0) {
            func_table[i].ref_row[func_table[i].ref_num++] = row;
            return &func_table[i];
        }
    }
    return NULL;
}

NormalElement *normal_table_lookup(char *tb)
{
    int i;
    // Reverse looking up.
    for (i = normal_table_p - 1; i >= 0; i--) {
        if ((normal_table[i].scope == current_scope || normal_table[i].scope == NULL) &&
            strcmp(normal_table[i].name, tb) == 0) {
                normal_table[i].ref_row[normal_table[i].ref_num++] = row;
                return &normal_table[i];
        }
    }
    return NULL;
}

void update_scope()
{
    current_scope = &func_table[func_table_p];
}

void table_ref_check()
{
    int i;
    Bool main_found = FALSE;
    for (i = 0; i < normal_table_p; i++) {
        if (normal_table[i].ref_num == 0 &&
            normal_table[i].clss != TEMP_VAR &&
            normal_table[i].clss != PARAMETER) {
                fprintf(error, "Row %d: Declared but \'%s\' is never used.\n", normal_table[i].dcl_row, normal_table[i].name);
        }
    }
    for (i = 0; i < func_table_p; i++) {
        if (func_table[i].ref_num == 0) {
            if (strcmp(func_table[i].name, "MAIN") != 0) {
                fprintf(error, "Row %d: Function \'%s\' is never called.\n", func_table[i].dcl_row, func_table[i].name);
            }
            else {
                main_found = TRUE;
            }
        }
    }
    if (main_found == FALSE) {
        print_warning("Function main is not found", "");
    }
}